// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/test_helper.h"

#include <algorithm>
#include <string>

#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "gpu/command_buffer/service/buffer_manager.h"
#include "gpu/command_buffer/service/error_state_mock.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/command_buffer/service/mocks.h"
#include "gpu/command_buffer/service/program_manager.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_mock.h"
#include "ui/gl/gl_version_info.h"

using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::MatcherCast;
using ::testing::NotNull;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SetArgumentPointee;
using ::testing::SetArrayArgument;
using ::testing::StrEq;
using ::testing::StrictMock;

namespace gpu {
namespace gles2 {

    namespace {

        template <typename T>
        T ConstructShaderVariable(
            GLenum type, GLint array_size, GLenum precision,
            bool static_use, const std::string& name)
        {
            T var;
            var.type = type;
            var.arraySize = array_size;
            var.precision = precision;
            var.staticUse = static_use;
            var.name = name;
            var.mappedName = name; // No name hashing.
            return var;
        }

    } // namespace anonymous

// GCC requires these declarations, but MSVC requires they not be present
#ifndef COMPILER_MSVC
    const GLuint TestHelper::kServiceBlackTexture2dId;
    const GLuint TestHelper::kServiceDefaultTexture2dId;
    const GLuint TestHelper::kServiceBlackTexture3dId;
    const GLuint TestHelper::kServiceDefaultTexture3dId;
    const GLuint TestHelper::kServiceBlackTexture2dArrayId;
    const GLuint TestHelper::kServiceDefaultTexture2dArrayId;
    const GLuint TestHelper::kServiceBlackTextureCubemapId;
    const GLuint TestHelper::kServiceDefaultTextureCubemapId;
    const GLuint TestHelper::kServiceBlackExternalTextureId;
    const GLuint TestHelper::kServiceDefaultExternalTextureId;
    const GLuint TestHelper::kServiceBlackRectangleTextureId;
    const GLuint TestHelper::kServiceDefaultRectangleTextureId;

    const GLint TestHelper::kMaxSamples;
    const GLint TestHelper::kMaxRenderbufferSize;
    const GLint TestHelper::kMaxTextureSize;
    const GLint TestHelper::kMaxCubeMapTextureSize;
    const GLint TestHelper::kMaxRectangleTextureSize;
    const GLint TestHelper::kMax3DTextureSize;
    const GLint TestHelper::kNumVertexAttribs;
    const GLint TestHelper::kNumTextureUnits;
    const GLint TestHelper::kMaxTextureImageUnits;
    const GLint TestHelper::kMaxVertexTextureImageUnits;
    const GLint TestHelper::kMaxFragmentUniformVectors;
    const GLint TestHelper::kMaxFragmentUniformComponents;
    const GLint TestHelper::kMaxVaryingVectors;
    const GLint TestHelper::kMaxVaryingFloats;
    const GLint TestHelper::kMaxVertexUniformVectors;
    const GLint TestHelper::kMaxVertexUniformComponents;
#endif

    std::vector<std::string> TestHelper::split_extensions_;

    void TestHelper::SetupTextureInitializationExpectations(
        ::gfx::MockGLInterface* gl,
        GLenum target,
        bool use_default_textures)
    {
        InSequence sequence;

        bool needs_initialization = (target != GL_TEXTURE_EXTERNAL_OES);
        bool needs_faces = (target == GL_TEXTURE_CUBE_MAP);
        bool is_3d_or_2d_array_target = (target == GL_TEXTURE_3D || target == GL_TEXTURE_2D_ARRAY);

        static GLuint texture_2d_ids[] = {
            kServiceBlackTexture2dId,
            kServiceDefaultTexture2dId
        };
        static GLuint texture_3d_ids[] = {
            kServiceBlackTexture3dId,
            kServiceDefaultTexture3dId
        };
        static GLuint texture_2d_array_ids[] = {
            kServiceBlackTexture2dArrayId,
            kServiceDefaultTexture2dArrayId
        };
        static GLuint texture_cube_map_ids[] = {
            kServiceBlackTextureCubemapId,
            kServiceDefaultTextureCubemapId
        };
        static GLuint texture_external_oes_ids[] = {
            kServiceBlackExternalTextureId,
            kServiceDefaultExternalTextureId
        };
        static GLuint texture_rectangle_arb_ids[] = {
            kServiceBlackRectangleTextureId,
            kServiceDefaultRectangleTextureId
        };

        const GLuint* texture_ids = NULL;
        switch (target) {
        case GL_TEXTURE_2D:
            texture_ids = &texture_2d_ids[0];
            break;
        case GL_TEXTURE_3D:
            texture_ids = &texture_3d_ids[0];
            break;
        case GL_TEXTURE_2D_ARRAY:
            texture_ids = &texture_2d_array_ids[0];
            break;
        case GL_TEXTURE_CUBE_MAP:
            texture_ids = &texture_cube_map_ids[0];
            break;
        case GL_TEXTURE_EXTERNAL_OES:
            texture_ids = &texture_external_oes_ids[0];
            break;
        case GL_TEXTURE_RECTANGLE_ARB:
            texture_ids = &texture_rectangle_arb_ids[0];
            break;
        default:
            NOTREACHED();
        }

        int array_size = use_default_textures ? 2 : 1;

        EXPECT_CALL(*gl, GenTextures(array_size, _))
            .WillOnce(SetArrayArgument<1>(texture_ids,
                texture_ids + array_size))
            .RetiresOnSaturation();
        for (int ii = 0; ii < array_size; ++ii) {
            EXPECT_CALL(*gl, BindTexture(target, texture_ids[ii]))
                .Times(1)
                .RetiresOnSaturation();
            if (needs_initialization) {
                if (needs_faces) {
                    static GLenum faces[] = {
                        GL_TEXTURE_CUBE_MAP_POSITIVE_X,
                        GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
                        GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                        GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
                        GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
                        GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
                    };
                    for (size_t ii = 0; ii < arraysize(faces); ++ii) {
                        EXPECT_CALL(*gl, TexImage2D(faces[ii], 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, _))
                            .Times(1)
                            .RetiresOnSaturation();
                    }
                } else {
                    if (is_3d_or_2d_array_target) {
                        EXPECT_CALL(*gl, TexImage3D(target, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, _))
                            .Times(1)
                            .RetiresOnSaturation();
                    } else {
                        EXPECT_CALL(*gl, TexImage2D(target, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, _))
                            .Times(1)
                            .RetiresOnSaturation();
                    }
                }
            }
        }
        EXPECT_CALL(*gl, BindTexture(target, 0))
            .Times(1)
            .RetiresOnSaturation();
    }

    void TestHelper::SetupTextureManagerInitExpectations(
        ::gfx::MockGLInterface* gl,
        bool is_es3_enabled,
        const char* extensions,
        bool use_default_textures)
    {
        InSequence sequence;

        SetupTextureInitializationExpectations(
            gl, GL_TEXTURE_2D, use_default_textures);
        SetupTextureInitializationExpectations(
            gl, GL_TEXTURE_CUBE_MAP, use_default_textures);

        if (is_es3_enabled) {
            SetupTextureInitializationExpectations(
                gl, GL_TEXTURE_3D, use_default_textures);
            SetupTextureInitializationExpectations(
                gl, GL_TEXTURE_2D_ARRAY, use_default_textures);
        }

        bool ext_image_external = false;
        bool arb_texture_rectangle = false;
        base::CStringTokenizer t(extensions, extensions + strlen(extensions), " ");
        while (t.GetNext()) {
            if (t.token() == "GL_OES_EGL_image_external") {
                ext_image_external = true;
                break;
            }
            if (t.token() == "GL_ARB_texture_rectangle") {
                arb_texture_rectangle = true;
                break;
            }
        }

        if (ext_image_external) {
            SetupTextureInitializationExpectations(
                gl, GL_TEXTURE_EXTERNAL_OES, use_default_textures);
        }
        if (arb_texture_rectangle) {
            SetupTextureInitializationExpectations(
                gl, GL_TEXTURE_RECTANGLE_ARB, use_default_textures);
        }
    }

    void TestHelper::SetupTextureDestructionExpectations(
        ::gfx::MockGLInterface* gl,
        GLenum target,
        bool use_default_textures)
    {
        if (!use_default_textures)
            return;

        GLuint texture_id = 0;
        switch (target) {
        case GL_TEXTURE_2D:
            texture_id = kServiceDefaultTexture2dId;
            break;
        case GL_TEXTURE_3D:
            texture_id = kServiceDefaultTexture3dId;
            break;
        case GL_TEXTURE_2D_ARRAY:
            texture_id = kServiceDefaultTexture2dArrayId;
            break;
        case GL_TEXTURE_CUBE_MAP:
            texture_id = kServiceDefaultTextureCubemapId;
            break;
        case GL_TEXTURE_EXTERNAL_OES:
            texture_id = kServiceDefaultExternalTextureId;
            break;
        case GL_TEXTURE_RECTANGLE_ARB:
            texture_id = kServiceDefaultRectangleTextureId;
            break;
        default:
            NOTREACHED();
        }

        EXPECT_CALL(*gl, DeleteTextures(1, Pointee(texture_id)))
            .Times(1)
            .RetiresOnSaturation();
    }

    void TestHelper::SetupTextureManagerDestructionExpectations(
        ::gfx::MockGLInterface* gl,
        bool is_es3_enabled,
        const char* extensions,
        bool use_default_textures)
    {
        SetupTextureDestructionExpectations(gl, GL_TEXTURE_2D, use_default_textures);
        SetupTextureDestructionExpectations(
            gl, GL_TEXTURE_CUBE_MAP, use_default_textures);

        if (is_es3_enabled) {
            SetupTextureDestructionExpectations(
                gl, GL_TEXTURE_3D, use_default_textures);
            SetupTextureDestructionExpectations(
                gl, GL_TEXTURE_2D_ARRAY, use_default_textures);
        }

        bool ext_image_external = false;
        bool arb_texture_rectangle = false;
        base::CStringTokenizer t(extensions, extensions + strlen(extensions), " ");
        while (t.GetNext()) {
            if (t.token() == "GL_OES_EGL_image_external") {
                ext_image_external = true;
                break;
            }
            if (t.token() == "GL_ARB_texture_rectangle") {
                arb_texture_rectangle = true;
                break;
            }
        }

        if (ext_image_external) {
            SetupTextureDestructionExpectations(
                gl, GL_TEXTURE_EXTERNAL_OES, use_default_textures);
        }
        if (arb_texture_rectangle) {
            SetupTextureDestructionExpectations(
                gl, GL_TEXTURE_RECTANGLE_ARB, use_default_textures);
        }

        EXPECT_CALL(*gl, DeleteTextures(TextureManager::kNumDefaultTextures, _))
            .Times(1)
            .RetiresOnSaturation();
    }

    void TestHelper::SetupContextGroupInitExpectations(
        ::gfx::MockGLInterface* gl,
        const DisallowedFeatures& disallowed_features,
        const char* extensions,
        const char* gl_version,
        bool bind_generates_resource)
    {
        InSequence sequence;

        SetupFeatureInfoInitExpectationsWithGLVersion(gl, extensions, "", gl_version);

        gfx::GLVersionInfo gl_info(gl_version, "", extensions);

        EXPECT_CALL(*gl, GetIntegerv(GL_MAX_RENDERBUFFER_SIZE, _))
            .WillOnce(SetArgumentPointee<1>(kMaxRenderbufferSize))
            .RetiresOnSaturation();
        if (strstr(extensions, "GL_EXT_framebuffer_multisample") || strstr(extensions, "GL_EXT_multisampled_render_to_texture") || gl_info.is_es3) {
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_SAMPLES, _))
                .WillOnce(SetArgumentPointee<1>(kMaxSamples))
                .RetiresOnSaturation();
        } else if (strstr(extensions, "GL_IMG_multisampled_render_to_texture")) {
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_SAMPLES_IMG, _))
                .WillOnce(SetArgumentPointee<1>(kMaxSamples))
                .RetiresOnSaturation();
        }
        EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_ATTRIBS, _))
            .WillOnce(SetArgumentPointee<1>(kNumVertexAttribs))
            .RetiresOnSaturation();
        EXPECT_CALL(*gl, GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, _))
            .WillOnce(SetArgumentPointee<1>(kNumTextureUnits))
            .RetiresOnSaturation();
        EXPECT_CALL(*gl, GetIntegerv(GL_MAX_TEXTURE_SIZE, _))
            .WillOnce(SetArgumentPointee<1>(kMaxTextureSize))
            .RetiresOnSaturation();
        EXPECT_CALL(*gl, GetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, _))
            .WillOnce(SetArgumentPointee<1>(kMaxCubeMapTextureSize))
            .RetiresOnSaturation();
        if (gl_info.IsES3Capable()) {
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_3D_TEXTURE_SIZE, _))
                .WillOnce(SetArgumentPointee<1>(kMax3DTextureSize))
                .RetiresOnSaturation();
        }
        if (strstr(extensions, "GL_ARB_texture_rectangle")) {
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE, _))
                .WillOnce(SetArgumentPointee<1>(kMaxRectangleTextureSize))
                .RetiresOnSaturation();
        }
        EXPECT_CALL(*gl, GetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, _))
            .WillOnce(SetArgumentPointee<1>(kMaxTextureImageUnits))
            .RetiresOnSaturation();
        EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, _))
            .WillOnce(SetArgumentPointee<1>(kMaxVertexTextureImageUnits))
            .RetiresOnSaturation();

        if (gl_info.is_es) {
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, _))
                .WillOnce(SetArgumentPointee<1>(kMaxFragmentUniformVectors))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VARYING_VECTORS, _))
                .WillOnce(SetArgumentPointee<1>(kMaxVaryingVectors))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, _))
                .WillOnce(SetArgumentPointee<1>(kMaxVertexUniformVectors))
                .RetiresOnSaturation();
        } else {
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, _))
                .WillOnce(SetArgumentPointee<1>(kMaxFragmentUniformComponents))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VARYING_FLOATS, _))
                .WillOnce(SetArgumentPointee<1>(kMaxVaryingFloats))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, _))
                .WillOnce(SetArgumentPointee<1>(kMaxVertexUniformComponents))
                .RetiresOnSaturation();
        }

        bool use_default_textures = bind_generates_resource;
        SetupTextureManagerInitExpectations(
            gl, false, extensions, use_default_textures);
    }

    void TestHelper::SetupFeatureInfoInitExpectations(
        ::gfx::MockGLInterface* gl, const char* extensions)
    {
        SetupFeatureInfoInitExpectationsWithGLVersion(gl, extensions, "", "");
    }

    void TestHelper::SetupFeatureInfoInitExpectationsWithGLVersion(
        ::gfx::MockGLInterface* gl,
        const char* extensions,
        const char* gl_renderer,
        const char* gl_version)
    {
        InSequence sequence;

        EXPECT_CALL(*gl, GetString(GL_VERSION))
            .WillOnce(Return(reinterpret_cast<const uint8*>(gl_version)))
            .RetiresOnSaturation();

        // Persistent storage is needed for the split extension string.
        split_extensions_.clear();
        if (extensions) {
            split_extensions_ = base::SplitString(
                extensions, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
        }

        gfx::GLVersionInfo gl_info(gl_version, gl_renderer, extensions);
        if (!gl_info.is_es && gl_info.major_version >= 3) {
            EXPECT_CALL(*gl, GetIntegerv(GL_NUM_EXTENSIONS, _))
                .WillOnce(SetArgumentPointee<1>(split_extensions_.size()))
                .RetiresOnSaturation();
            for (size_t ii = 0; ii < split_extensions_.size(); ++ii) {
                EXPECT_CALL(*gl, GetStringi(GL_EXTENSIONS, ii))
                    .WillOnce(Return(reinterpret_cast<const uint8*>(
                        split_extensions_[ii].c_str())))
                    .RetiresOnSaturation();
            }
        } else {
            EXPECT_CALL(*gl, GetString(GL_EXTENSIONS))
                .WillOnce(Return(reinterpret_cast<const uint8*>(extensions)))
                .RetiresOnSaturation();
        }

        EXPECT_CALL(*gl, GetString(GL_VERSION))
            .WillOnce(Return(reinterpret_cast<const uint8*>(gl_version)))
            .RetiresOnSaturation();
        EXPECT_CALL(*gl, GetString(GL_RENDERER))
            .WillOnce(Return(reinterpret_cast<const uint8*>(gl_renderer)))
            .RetiresOnSaturation();

        if (strstr(extensions, "GL_ARB_texture_float") || (gl_info.is_es3 && strstr(extensions, "GL_EXT_color_buffer_float"))) {
            static const GLuint tx_ids[] = { 101, 102 };
            static const GLuint fb_ids[] = { 103, 104 };
            const GLsizei width = 16;
            EXPECT_CALL(*gl, GetIntegerv(GL_FRAMEBUFFER_BINDING, _))
                .WillOnce(SetArgumentPointee<1>(fb_ids[0]))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetIntegerv(GL_TEXTURE_BINDING_2D, _))
                .WillOnce(SetArgumentPointee<1>(tx_ids[0]))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GenTextures(1, _))
                .WillOnce(SetArrayArgument<1>(tx_ids + 1, tx_ids + 2))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GenFramebuffersEXT(1, _))
                .WillOnce(SetArrayArgument<1>(fb_ids + 1, fb_ids + 2))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[1]))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, width, 0, GL_RGBA, GL_FLOAT, _))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[1]))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tx_ids[1], 0))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
                .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, width, 0, GL_RGB, GL_FLOAT, _))
                .Times(1)
                .RetiresOnSaturation();
            if (gl_info.is_es3) {
                EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
                    .WillOnce(Return(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT))
                    .RetiresOnSaturation();
            } else {
                EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
                    .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
                    .RetiresOnSaturation();
            }
            EXPECT_CALL(*gl, DeleteFramebuffersEXT(1, _))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, DeleteTextures(1, _))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[0]))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[0]))
                .Times(1)
                .RetiresOnSaturation();
#if DCHECK_IS_ON()
            EXPECT_CALL(*gl, GetError())
                .WillOnce(Return(GL_NO_ERROR))
                .RetiresOnSaturation();
#endif
        }

        if (strstr(extensions, "GL_EXT_draw_buffers") || strstr(extensions, "GL_ARB_draw_buffers") || (gl_info.is_es3 && strstr(extensions, "GL_NV_draw_buffers"))) {
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, _))
                .WillOnce(SetArgumentPointee<1>(8))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetIntegerv(GL_MAX_DRAW_BUFFERS_ARB, _))
                .WillOnce(SetArgumentPointee<1>(8))
                .RetiresOnSaturation();
        }

        if (gl_info.is_es3 || strstr(extensions, "GL_EXT_texture_rg") || (strstr(extensions, "GL_ARB_texture_rg"))) {
            static const GLuint tx_ids[] = { 101, 102 };
            static const GLuint fb_ids[] = { 103, 104 };
            const GLsizei width = 1;
            EXPECT_CALL(*gl, GetIntegerv(GL_FRAMEBUFFER_BINDING, _))
                .WillOnce(SetArgumentPointee<1>(fb_ids[0]))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetIntegerv(GL_TEXTURE_BINDING_2D, _))
                .WillOnce(SetArgumentPointee<1>(tx_ids[0]))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GenTextures(1, _))
                .WillOnce(SetArrayArgument<1>(tx_ids + 1, tx_ids + 2))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[1]))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, TexImage2D(GL_TEXTURE_2D, 0, _, width, width, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, _))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GenFramebuffersEXT(1, _))
                .WillOnce(SetArrayArgument<1>(fb_ids + 1, fb_ids + 2))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[1]))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, FramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tx_ids[1], 0))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, CheckFramebufferStatusEXT(GL_FRAMEBUFFER))
                .WillOnce(Return(GL_FRAMEBUFFER_COMPLETE))
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, DeleteFramebuffersEXT(1, _))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, DeleteTextures(1, _))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindFramebufferEXT(GL_FRAMEBUFFER, fb_ids[0]))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, BindTexture(GL_TEXTURE_2D, tx_ids[0]))
                .Times(1)
                .RetiresOnSaturation();
#if DCHECK_IS_ON()
            EXPECT_CALL(*gl, GetError())
                .WillOnce(Return(GL_NO_ERROR))
                .RetiresOnSaturation();
#endif
        }
    }

    void TestHelper::SetupExpectationsForClearingUniforms(
        ::gfx::MockGLInterface* gl, UniformInfo* uniforms, size_t num_uniforms)
    {
        for (size_t ii = 0; ii < num_uniforms; ++ii) {
            const UniformInfo& info = uniforms[ii];
            switch (info.type) {
            case GL_FLOAT:
                EXPECT_CALL(*gl, Uniform1fv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_FLOAT_VEC2:
                EXPECT_CALL(*gl, Uniform2fv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_FLOAT_VEC3:
                EXPECT_CALL(*gl, Uniform3fv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_FLOAT_VEC4:
                EXPECT_CALL(*gl, Uniform4fv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_INT:
            case GL_BOOL:
            case GL_SAMPLER_2D:
            case GL_SAMPLER_CUBE:
            case GL_SAMPLER_EXTERNAL_OES:
            case GL_SAMPLER_3D_OES:
            case GL_SAMPLER_2D_RECT_ARB:
            case GL_SAMPLER_2D_ARRAY:
                EXPECT_CALL(*gl, Uniform1iv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_UNSIGNED_INT:
                EXPECT_CALL(*gl, Uniform1uiv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_INT_VEC2:
            case GL_BOOL_VEC2:
                EXPECT_CALL(*gl, Uniform2iv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_UNSIGNED_INT_VEC2:
                EXPECT_CALL(*gl, Uniform2uiv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_INT_VEC3:
            case GL_BOOL_VEC3:
                EXPECT_CALL(*gl, Uniform3iv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_UNSIGNED_INT_VEC3:
                EXPECT_CALL(*gl, Uniform3uiv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_INT_VEC4:
            case GL_BOOL_VEC4:
                EXPECT_CALL(*gl, Uniform4iv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_UNSIGNED_INT_VEC4:
                EXPECT_CALL(*gl, Uniform4uiv(info.real_location, info.size, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_FLOAT_MAT2:
                EXPECT_CALL(*gl, UniformMatrix2fv(info.real_location, info.size, false, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_FLOAT_MAT3:
                EXPECT_CALL(*gl, UniformMatrix3fv(info.real_location, info.size, false, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            case GL_FLOAT_MAT4:
                EXPECT_CALL(*gl, UniformMatrix4fv(info.real_location, info.size, false, _))
                    .Times(1)
                    .RetiresOnSaturation();
                break;
            default:
                NOTREACHED();
                break;
            }
        }
    }

    void TestHelper::SetupProgramSuccessExpectations(
        ::gfx::MockGLInterface* gl,
        AttribInfo* attribs, size_t num_attribs,
        UniformInfo* uniforms, size_t num_uniforms,
        GLuint service_id)
    {
        EXPECT_CALL(*gl,
            GetProgramiv(service_id, GL_LINK_STATUS, _))
            .WillOnce(SetArgumentPointee<2>(1))
            .RetiresOnSaturation();
        EXPECT_CALL(*gl,
            GetProgramiv(service_id, GL_INFO_LOG_LENGTH, _))
            .WillOnce(SetArgumentPointee<2>(0))
            .RetiresOnSaturation();
        EXPECT_CALL(*gl,
            GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTES, _))
            .WillOnce(SetArgumentPointee<2>(num_attribs))
            .RetiresOnSaturation();
        size_t max_attrib_len = 0;
        for (size_t ii = 0; ii < num_attribs; ++ii) {
            size_t len = strlen(attribs[ii].name) + 1;
            max_attrib_len = std::max(max_attrib_len, len);
        }
        EXPECT_CALL(*gl,
            GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, _))
            .WillOnce(SetArgumentPointee<2>(max_attrib_len))
            .RetiresOnSaturation();

        for (size_t ii = 0; ii < num_attribs; ++ii) {
            const AttribInfo& info = attribs[ii];
            EXPECT_CALL(*gl,
                GetActiveAttrib(service_id, ii,
                    max_attrib_len, _, _, _, _))
                .WillOnce(DoAll(
                    SetArgumentPointee<3>(strlen(info.name)),
                    SetArgumentPointee<4>(info.size),
                    SetArgumentPointee<5>(info.type),
                    SetArrayArgument<6>(info.name,
                        info.name + strlen(info.name) + 1)))
                .RetiresOnSaturation();
            if (!ProgramManager::IsInvalidPrefix(info.name, strlen(info.name))) {
                EXPECT_CALL(*gl, GetAttribLocation(service_id, StrEq(info.name)))
                    .WillOnce(Return(info.location))
                    .RetiresOnSaturation();
            }
        }
        EXPECT_CALL(*gl,
            GetProgramiv(service_id, GL_ACTIVE_UNIFORMS, _))
            .WillOnce(SetArgumentPointee<2>(num_uniforms))
            .RetiresOnSaturation();

        size_t max_uniform_len = 0;
        for (size_t ii = 0; ii < num_uniforms; ++ii) {
            size_t len = strlen(uniforms[ii].name) + 1;
            max_uniform_len = std::max(max_uniform_len, len);
        }
        EXPECT_CALL(*gl,
            GetProgramiv(service_id, GL_ACTIVE_UNIFORM_MAX_LENGTH, _))
            .WillOnce(SetArgumentPointee<2>(max_uniform_len))
            .RetiresOnSaturation();
        for (size_t ii = 0; ii < num_uniforms; ++ii) {
            const UniformInfo& info = uniforms[ii];
            EXPECT_CALL(*gl,
                GetActiveUniform(service_id, ii,
                    max_uniform_len, _, _, _, _))
                .WillOnce(DoAll(
                    SetArgumentPointee<3>(strlen(info.name)),
                    SetArgumentPointee<4>(info.size),
                    SetArgumentPointee<5>(info.type),
                    SetArrayArgument<6>(info.name,
                        info.name + strlen(info.name) + 1)))
                .RetiresOnSaturation();
        }

        for (int pass = 0; pass < 2; ++pass) {
            for (size_t ii = 0; ii < num_uniforms; ++ii) {
                const UniformInfo& info = uniforms[ii];
                if (pass == 0 && info.real_location != -1) {
                    EXPECT_CALL(*gl, GetUniformLocation(service_id, StrEq(info.name)))
                        .WillOnce(Return(info.real_location))
                        .RetiresOnSaturation();
                }
                if ((pass == 0 && info.desired_location >= 0) || (pass == 1 && info.desired_location < 0)) {
                    if (info.size > 1) {
                        std::string base_name = info.name;
                        size_t array_pos = base_name.rfind("[0]");
                        if (base_name.size() > 3 && array_pos == base_name.size() - 3) {
                            base_name = base_name.substr(0, base_name.size() - 3);
                        }
                        for (GLsizei jj = 1; jj < info.size; ++jj) {
                            std::string element_name(
                                std::string(base_name) + "[" + base::IntToString(jj) + "]");
                            EXPECT_CALL(*gl, GetUniformLocation(service_id, StrEq(element_name)))
                                .WillOnce(Return(info.real_location + jj * 2))
                                .RetiresOnSaturation();
                        }
                    }
                }
            }
        }
    }

    void TestHelper::SetupShader(
        ::gfx::MockGLInterface* gl,
        AttribInfo* attribs, size_t num_attribs,
        UniformInfo* uniforms, size_t num_uniforms,
        GLuint service_id)
    {
        InSequence s;

        EXPECT_CALL(*gl,
            LinkProgram(service_id))
            .Times(1)
            .RetiresOnSaturation();

        SetupProgramSuccessExpectations(
            gl, attribs, num_attribs, uniforms, num_uniforms, service_id);
    }

    void TestHelper::DoBufferData(
        ::gfx::MockGLInterface* gl, MockErrorState* error_state,
        BufferManager* manager, Buffer* buffer, GLenum target, GLsizeiptr size,
        GLenum usage, const GLvoid* data, GLenum error)
    {
        EXPECT_CALL(*error_state, CopyRealGLErrorsToWrapper(_, _, _))
            .Times(1)
            .RetiresOnSaturation();
        if (manager->IsUsageClientSideArray(usage)) {
            EXPECT_CALL(*gl, BufferData(target, 0, _, usage))
                .Times(1)
                .RetiresOnSaturation();
        } else {
            EXPECT_CALL(*gl, BufferData(target, size, _, usage))
                .Times(1)
                .RetiresOnSaturation();
        }
        EXPECT_CALL(*error_state, PeekGLError(_, _, _))
            .WillOnce(Return(error))
            .RetiresOnSaturation();
        manager->DoBufferData(error_state, buffer, target, size, usage, data);
    }

    void TestHelper::SetTexParameteriWithExpectations(
        ::gfx::MockGLInterface* gl, MockErrorState* error_state,
        TextureManager* manager, TextureRef* texture_ref,
        GLenum pname, GLint value, GLenum error)
    {
        if (error == GL_NO_ERROR) {
            if (pname != GL_TEXTURE_POOL_CHROMIUM) {
                EXPECT_CALL(*gl, TexParameteri(texture_ref->texture()->target(), pname, value))
                    .Times(1)
                    .RetiresOnSaturation();
            }
        } else if (error == GL_INVALID_ENUM) {
            EXPECT_CALL(*error_state, SetGLErrorInvalidEnum(_, _, _, value, _))
                .Times(1)
                .RetiresOnSaturation();
        } else {
            EXPECT_CALL(*error_state, SetGLErrorInvalidParami(_, _, error, _, _, _))
                .Times(1)
                .RetiresOnSaturation();
        }
        manager->SetParameteri("", error_state, texture_ref, pname, value);
    }

    // static
    void TestHelper::SetShaderStates(
        ::gfx::MockGLInterface* gl, Shader* shader,
        bool expected_valid,
        const std::string* const expected_log_info,
        const std::string* const expected_translated_source,
        const int* const expected_shader_version,
        const AttributeMap* const expected_attrib_map,
        const UniformMap* const expected_uniform_map,
        const VaryingMap* const expected_varying_map,
        const NameMap* const expected_name_map)
    {
        const std::string empty_log_info;
        const std::string* log_info = (expected_log_info && !expected_valid) ? expected_log_info : &empty_log_info;
        const std::string empty_translated_source;
        const std::string* translated_source = (expected_translated_source && expected_valid) ? expected_translated_source : &empty_translated_source;
        int default_shader_version = 100;
        const int* shader_version = (expected_shader_version && expected_valid) ? expected_shader_version : &default_shader_version;
        const AttributeMap empty_attrib_map;
        const AttributeMap* attrib_map = (expected_attrib_map && expected_valid) ? expected_attrib_map : &empty_attrib_map;
        const UniformMap empty_uniform_map;
        const UniformMap* uniform_map = (expected_uniform_map && expected_valid) ? expected_uniform_map : &empty_uniform_map;
        const VaryingMap empty_varying_map;
        const VaryingMap* varying_map = (expected_varying_map && expected_valid) ? expected_varying_map : &empty_varying_map;
        const NameMap empty_name_map;
        const NameMap* name_map = (expected_name_map && expected_valid) ? expected_name_map : &empty_name_map;

        MockShaderTranslator* mock_translator = new MockShaderTranslator;
        scoped_refptr<ShaderTranslatorInterface> translator(mock_translator);
        EXPECT_CALL(*mock_translator, Translate(_,
                                          NotNull(), // log_info
                                          NotNull(), // translated_source
                                          NotNull(), // shader_version
                                          NotNull(), // attrib_map
                                          NotNull(), // uniform_map
                                          NotNull(), // varying_map
                                          NotNull())) // name_map
            .WillOnce(DoAll(SetArgumentPointee<1>(*log_info),
                SetArgumentPointee<2>(*translated_source),
                SetArgumentPointee<3>(*shader_version),
                SetArgumentPointee<4>(*attrib_map),
                SetArgumentPointee<5>(*uniform_map),
                SetArgumentPointee<6>(*varying_map),
                SetArgumentPointee<7>(*name_map),
                Return(expected_valid)))
            .RetiresOnSaturation();
        if (expected_valid) {
            EXPECT_CALL(*gl, ShaderSource(shader->service_id(), 1, _, NULL))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, CompileShader(shader->service_id()))
                .Times(1)
                .RetiresOnSaturation();
            EXPECT_CALL(*gl, GetShaderiv(shader->service_id(), GL_COMPILE_STATUS,
                                 NotNull())) // status
                .WillOnce(SetArgumentPointee<2>(GL_TRUE))
                .RetiresOnSaturation();
        }
        shader->RequestCompile(translator, Shader::kGL);
        shader->DoCompile();
    }

    // static
    void TestHelper::SetShaderStates(
        ::gfx::MockGLInterface* gl, Shader* shader, bool valid)
    {
        SetShaderStates(gl, shader, valid, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
    }

    // static
    sh::Attribute TestHelper::ConstructAttribute(
        GLenum type, GLint array_size, GLenum precision,
        bool static_use, const std::string& name)
    {
        return ConstructShaderVariable<sh::Attribute>(
            type, array_size, precision, static_use, name);
    }

    // static
    sh::Uniform TestHelper::ConstructUniform(
        GLenum type, GLint array_size, GLenum precision,
        bool static_use, const std::string& name)
    {
        return ConstructShaderVariable<sh::Uniform>(
            type, array_size, precision, static_use, name);
    }

    // static
    sh::Varying TestHelper::ConstructVarying(
        GLenum type, GLint array_size, GLenum precision,
        bool static_use, const std::string& name)
    {
        return ConstructShaderVariable<sh::Varying>(
            type, array_size, precision, static_use, name);
    }

    ScopedGLImplementationSetter::ScopedGLImplementationSetter(
        gfx::GLImplementation implementation)
        : old_implementation_(gfx::GetGLImplementation())
    {
        gfx::SetGLImplementation(implementation);
    }

    ScopedGLImplementationSetter::~ScopedGLImplementationSetter()
    {
        gfx::SetGLImplementation(old_implementation_);
    }

} // namespace gles2
} // namespace gpu
